package com.pgp.demo.pgpexample.util;
import cn.hutool.core.io.resource.ClassPathResource;
import cn.hutool.core.text.CharSequenceUtil;
import cn.hutool.extra.ssh.JschRuntimeException;
import cn.hutool.extra.ssh.JschUtil;
import com.jcraft.jsch.*;
import com.pgp.demo.pgpexample.dto.SftpConfigDTO;
import com.pgp.demo.pgpexample.exception.BusinessException;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import java.io.*;
/**
 * sftp util
 *
 * @author: alan
 * @date: 2024/01/23
 */
@Slf4j
public class SFTPConnectUtil {
    @Getter
    private ChannelSftp sftp;
    private Session session;

    /**
     * 连接 sftp
     *
     * @param sftpConfigDTO sftp 配置 DTO
     * @throw {@link BusinessException} 连接异常
     */
    public void connect(SftpConfigDTO sftpConfigDTO) {
        String mark = CharSequenceUtil.format(" [{}@{}] privateKeyPath:{}", sftpConfigDTO.getUsername(), sftpConfigDTO.getHost(), sftpConfigDTO.getPrivateKeyPath());
        long currentSessionTime = System.currentTimeMillis();
        JSch jsch = new JSch();
        try {
            // 设置密钥
            jsch.addIdentity("openssh_private_key", getPrivateKeyByte(sftpConfigDTO.getPrivateKeyPath(), mark), null, null);
            // 创建session
            session = jsch.getSession(sftpConfigDTO.getUsername(), sftpConfigDTO.getHost(), sftpConfigDTO.getPort());
            session.setConfig("StrictHostKeyChecking", "no");
            session.setConfig("userauth.gssapi-with-mic", "no");
            // 固定 认证模式
            session.setConfig("PreferredAuthentications", "publickey");
            // 连接ssh 并设置超时时间
            session.connect(sftpConfigDTO.getConnectTimeout());
            log.info("[session] 尝试连接session {}  {}ms", mark, System.currentTimeMillis() - currentSessionTime);
        } catch (JSchException e) {
            log.error("[session] 尝试连接sftp失败 server {} error:{}", mark, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("[SFTP] session连接未知异常", e);
        }

        try {
            long currentChannelTime = System.currentTimeMillis();
            log.info("[channelSftp] 尝试连接channel {} {}ms", mark, System.currentTimeMillis() - currentChannelTime);
            sftp = JschUtil.openSftp(session, sftpConfigDTO.getConnectTimeout());
        } catch (JschRuntimeException je) {
            log.error("[channelSftp] 尝试连接channelSftp失败 server {} error:{}", mark, ExceptionUtils.getStackTrace(je));
            throw new BusinessException("[SFTP] channel连接未知异常", je);
        }
    }
    private byte[] getPrivateKeyByte(String path, String mark){
        try {
            ClassPathResource classPathResource = new ClassPathResource(path);
            return IOUtils.toByteArray(classPathResource.getStream());
        } catch (Exception e) {
            log.error("ClassPathResource getStream {} IO error:{}", mark, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("[SFTP] session读取密钥未知异常", e);
        }
    }
    /**
     * 关闭连接 server
     */
    public void disconnect(){
        if (sftp != null && (sftp.isConnected())) {
            sftp.disconnect();
            log.info("[SFTP] sftp is closed already");
        }
        if (session != null && (session.isConnected())) {
            session.disconnect();
            log.info("[SFTP] sshSession is closed already");
        }
    }

    /**
     * 将输入流的数据上传到sftp作为文件
     *
     * @param directory    目录 /root
     * @param fileName     文件名 a.xls
     * @param inputStream  输入
     * @throw {@link BusinessException} 文件上传 未知异常
     */
    public void upload(String directory, String fileName, InputStream inputStream) {
        try {
            // 进入上传目录
            sftp.cd(directory);
        } catch (SftpException e) {
            throw new BusinessException(CharSequenceUtil.format("[SFTP] 文件上传 directory:[{}] is null", directory));
        }
        try {
            sftp.put(inputStream, fileName);
            log.info("[SFTP] fileName:{} upload successful" , fileName);
        } catch (SftpException e) {
            log.error("[SFTP] 文件上传 未知异常 directory:[{}] fileName:[{}], error:{}", directory, fileName, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("[SFTP] 文件上传 未知异常", e);
        }
    }

    /**
     * 下载
     *
     * @param filePath 文件目录 /root/citic/b/test.xls
     * @return {@link File 原始文件}
     * @throw {@link BusinessException} 文件下载 未知异常
     */
    public File download(String filePath) {
        // 切割出目录and文件名
        int index = filePath.lastIndexOf("/");
        String directory = filePath.substring(0, index + 1);
        String fileName = filePath.substring(index + 1);
        try {
            // cd 到下载目录
            sftp.cd(directory);
        } catch (SftpException sftpException) {
            log.error("[SFTP] cd 到下载目录 未知异常 cd:{}, error:{}", directory, ExceptionUtils.getStackTrace(sftpException));
            throw new BusinessException("cd 到下载目录 未知异常", sftpException);
        }
        // 文件名
        File file = new File(fileName);
        FileOutputStream fileOutputStream;
        try {
            fileOutputStream =  new FileOutputStream(file);
        } catch (FileNotFoundException e) {
            log.error("[SFTP] 文件不存在 Upload file failure. directory: {}, error:{}", directory, ExceptionUtils.getStackTrace(e));
            throw new BusinessException("[SFTP] 文件不存在", e);
        }
        try {
            // 获取目录下的文件名 转成流进行下载
            sftp.get(fileName, fileOutputStream);
        } catch (SftpException sftpException) {
            log.error("[SFTP] 获取目录下的文件 未知异常 fileName:{}, error:{}", fileName, ExceptionUtils.getStackTrace(sftpException));
            throw new BusinessException("获取目录下的文件 未知异常", sftpException);
        } finally {
            try {
                fileOutputStream.close();
            } catch (IOException e) {
                log.warn("资源关闭异常 error:{}", ExceptionUtils.getStackTrace(e));
            }
        }
        log.info("[SFTP] file:{}{} download successful" , directory, fileName);
        return file;
    }

    /**
     * 检查文件是否存在
     *
     * @param filePath 文件路径 /root/citic/b/test.xls
     * @return boolean
     */
    public boolean checkFileExist(String filePath) {
        try {
            // 检索文件夹或文件的属性
            sftp.stat(filePath);
            return true;
        } catch (SftpException e) {
            if (e.id == 2) {
                log.debug("No such file filePath:{}", filePath);
            } else {
                log.error("Check file exist failure. filePath: {}, error:{}", filePath, ExceptionUtils.getStackTrace(e));
            }
            return false;
        }
    }

}
